package main

import (
	proto "5_etcd_exp/helloworld"
	"log"

	// 标准库
	"context"
	"flag"
	"fmt"
	eclient "go.etcd.io/etcd/client/v3"
	"go.etcd.io/etcd/client/v3/naming/endpoints"
	"net"
	"time"

	// grpc 桩代码
	//"github.com/grpc_demo/proto"

	// grpc
	"google.golang.org/grpc"
)

const (
	// grpc 服务名
	MyService = "xiaoxu/demo"
	// etcd 端口
	MyEtcdURL = "http://localhost:2379"
)

type Server struct {
	proto.UnimplementedHelloServiceServer
}

// SayHello implements helloworld.GreeterServer
func (s *Server) SayHello(ctx context.Context, in *proto.HelloReq) (*proto.HelloReply, error) {
	log.Printf("Received: %v", in.GetName())
	return &proto.HelloReply{Message: "Hello " + in.GetName()}, nil
}

// 1. 启动服务
func main() {
	// 接收命令行指定的 grpc 服务端口
	var port int
	flag.IntVar(&port, "port", 8080, "port")
	flag.Parse()
	//addr := fmt.Sprintf("http://localhost:%d", port)
	addr := fmt.Sprintf("localhost:%d", port)

	// 创建 tcp 端口监听器
	listener, _ := net.Listen("tcp", addr)

	// 创建 grpc server
	server := grpc.NewServer()
	proto.RegisterHelloServiceServer(server, &Server{})
	log.Printf("server listening at %v", listener.Addr())

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// 注册 grpc 服务节点到 etcd 中
	go registerEndPointToEtcd(ctx, addr)

	// 启动 grpc 服务
	if err := server.Serve(listener); err != nil {
		fmt.Println(err)
	}

}

// 2. 服务注册
// registerEndPointToEtcd 方法给出了将 grpc 服务端节点注册到 etcd 模块的示例：
// eclient.NewFromURL 创建 etcd 客户端
// etcdClientendpoints.NewManager 创建 etcd 服务端节点管理模块
// etcdManageretcdClient.Grant 申请一份租约，租约的有效时间为 ttl
// etcdManager.AddEndpoint 将当前节点注册到 etcd 中，同时会和租约进行关联etcdClient.KeepAliveOnce 对租约进行一轮续期，重置租约失效的 ttl
func registerEndPointToEtcd(ctx context.Context, addr string) {
	// 创建 etcd 客户端
	etcdClient, _ := eclient.NewFromURL(MyEtcdURL)
	etcdManager, _ := endpoints.NewManager(etcdClient, MyService)

	// 创建一个租约，每隔 10s 需要向 etcd 汇报一次心跳，证明当前节点仍然存活
	var ttl int64 = 10
	lease, _ := etcdClient.Grant(ctx, ttl)

	// 添加注册节点到 etcd 中，并且携带上租约 id
	_ = etcdManager.AddEndpoint(ctx, fmt.Sprintf("%s/%s", MyService, addr), endpoints.Endpoint{Addr: addr}, eclient.WithLease(lease.ID))

	// 每隔 5 s进行一次延续租约的动作
	for {
		select {
		case <-time.After(5 * time.Second):
			// 续约操作
			resp, _ := etcdClient.KeepAliveOnce(ctx, lease.ID)
			fmt.Println("keep alive resp: %+v", resp)
		case <-ctx.Done():
			return
		}
	}
}

// 3. 注册节点
// 在 grpc 服务端注册 endpoint 时，调用了方法链 endpointManager.AddEndpoint -> endpointManager.Update，将服务节点 endpoint 以共同的服务名作为标识键 key 的前缀，添加到 kv 存储介质当中.
// 由于 endpoint 的注册信息关联到了租约，因此倘若租约过期，endpoint 的注册信息也随之失效. 所以 endpoint 在运行过程中，需要持续向 etcd 发送心跳以进行租约的续期，背后的作用正是通过这种续约机制向 etcd 服务注册模块证明 endpoint 自身的仍然处于存活状态.
//func (m *endpointManager) AddEndpoint(ctx context.Context, key string, endpoint Endpoint, opts ...clientv3.OpOption) error {
//	return m.Update(ctx, []*UpdateWithOpts{NewAddUpdateOpts(key, endpoint, opts...)})
//}
//func (m *endpointManager) Update(ctx context.Context, updates []*UpdateWithOpts) (err error) {
//	ops := make([]clientv3.Op, 0, len(updates))
//	for _, update := range updates {
//		if !strings.HasPrefix(update.Key, m.target+"/") {
//			return status.Errorf(codes.InvalidArgument, "endpoints: endpoint key should be prefixed with '%s/' got: '%s'", m.target, update.Key)
//		}
//
//		switch update.Op {
//		case Add:
//			internalUpdate := &internal.Update{
//				Op:       internal.Add,
//				Addr:     update.Endpoint.Addr,
//				Metadata: update.Endpoint.Metadata,
//			}
//
//			var v []byte
//			if v, err = json.Marshal(internalUpdate); err != nil {
//				return status.Error(codes.InvalidArgument, err.Error())
//			}
//			ops = append(ops, clientv3.OpPut(update.Key, string(v), update.Opts...))
//			// ...
//		}
//	}
//	_, err = m.client.KV.Txn(ctx).Then(ops...).Commit()
//	return err
//}
